﻿using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using LitJson;

#if UNITY_EDITOR
using UnityEditor;
#endif

public abstract class PUEXRender : MonoBehaviour {

	public enum SortType
	{
		ST_NONE,
		ST_FAR_FIRST,
		ST_NEAR_FIRST,
		ST_YOUNGEST_FIRST,
		ST_OLDEST_FIRST,
	};

	public enum BlendType
	{
		BT_NONE,
		BT_NORMAL,
		BT_ADD,
		BT_MULTIPLY,
	};

	protected static int __spawnIndex = 0;
	protected static Vector3 g_cameraPosForSort;
	
	#region - Editable property
	[Header("Render:")]
	public string renderName = "";
	public SortType sortType;
	public BlendType blendType;
	public Texture2D texture;
	public bool guiFX;
	public bool debugDraw;
	#endregion
	
	#region - Property
	[HideInInspector]
	public bool autoStart;
	[HideInInspector]
	protected bool _enabled;
	public bool isEnabled () {
		return _enabled;
	}
	public virtual void enable () {
		_enabled = true;
	}
	public virtual void disable () {
		_enabled = false;
	}

	[HideInInspector]
	public PUEXEmitter ownerEmitter;

	protected Material _material;
	#endregion
	
	#region - Lifecycle
	public PUEXRender ()
	{
		sortType = SortType.ST_NONE;
		blendType = BlendType.BT_NONE;
		guiFX = false;
		debugDraw = false;

		autoStart = true;
		_enabled = false;
	}

	public void Reset ()
	{
		// need emitter
		var emitter = gameObject.GetComponent<PUEXEmitter> ();
		if (emitter == null)
		{
			Debug.LogError("<PUEX> no emitter with render");
		}

		// only one render on one game object
		var renders = gameObject.GetComponents<PUEXRender> ();
		if (renders.Length > 1)
		{
			Debug.LogError("<PUEX> more than 1 render on gameobject");
		}

		// auto name
		if (renderName.Length == 0) {
			string typeName = GetType().Name;
			typeName = typeName.Replace("PUEX", "");
			renderName = gameObject.name + " " + typeName + " " + ++__spawnIndex;
		}
	}

	public void OnEnable ()
	{
		if (Application.isPlaying) {
			enable ();
		} else {
			autoStart = true;
		}
	}
	
	public void OnDisable ()
	{
		if (Application.isPlaying) {
			disable ();
		} else {
			autoStart = false;
		}
	}
	#endregion

	#region - State
	public virtual void prepared(PUEXEmitter emitter)
	{
		this.ownerEmitter = emitter;
	}
	
	public virtual void start ()
	{
		if (autoStart)
			enable ();
	}
	
	public virtual void pause ()
	{
	}
	
	public virtual void resume ()
	{
	}
	
	public virtual void stop ()
	{
		disable ();
	}
	
	public virtual void unPrepared()
	{
	}
	#endregion

	#region - Draw
	public virtual void visit ()
	{
		// sort particles for draw
		switch (sortType) {
		case SortType.ST_FAR_FIRST: {
			if (ownerEmitter.keepLocal) {
				Matrix4x4 invMat = ownerEmitter.ownerParticleSystem.systemToWorldTransform.inverse;
				g_cameraPosForSort = invMat.MultiplyPoint(Camera.current.transform.position);
			} else {
				g_cameraPosForSort = Camera.current.transform.position;
			}

			ownerEmitter.particlePool.Sort(sortFarFirst);

			break;
		}
		case SortType.ST_NEAR_FIRST: {
			if (ownerEmitter.keepLocal) {
				Matrix4x4 invMat = ownerEmitter.ownerParticleSystem.systemToWorldTransform.inverse;
				g_cameraPosForSort = invMat.MultiplyPoint(Camera.current.transform.position);
			} else {
				g_cameraPosForSort = Camera.current.transform.position;
			}
			
			ownerEmitter.particlePool.Sort(sortNearFirst);
			
			break;
		}
		case SortType.ST_YOUNGEST_FIRST: {
			ownerEmitter.particlePool.Sort(sortYoungestFirst);

			break;
		}
		case SortType.ST_OLDEST_FIRST: {
			ownerEmitter.particlePool.Sort(sortOldestFirst);

			break;
		}
		default:
			break;
		}
	}

	public virtual void draw ()
	{
	}

	public void OnDrawGizmos()
	{
		if (Application.isPlaying && enabled && debugDraw) {
			Matrix4x4 defaultMatrix = Gizmos.matrix;
			Matrix4x4 originalMatrix = Matrix4x4.identity;
			if (ownerEmitter.keepLocal) {
				originalMatrix = ownerEmitter.ownerParticleSystem.systemToWorldTransform;
			} else {
				originalMatrix = Matrix4x4.identity;
			}
			
			Color defaultColor = Gizmos.color;

			foreach (var particle in ownerEmitter.particlePool) {
				if (particle.alive) {
					Gizmos.matrix = originalMatrix * particle.transform();
					Vector3 dir = Vector3.forward;
					float length = 1f;
					PUEXUtils.drawCone(dir, 
					         Vector3.zero + dir * length, 
					         length, 
					         length * 0.5f, 
					         6, 
					         particle.color);
				}
			}
			
			Gizmos.color = defaultColor;
			Gizmos.matrix = defaultMatrix;
		}
	}

	protected static int sortFarFirst (PUEXParticle a, PUEXParticle b)
	{
		float a_d = (a.position - g_cameraPosForSort).sqrMagnitude;
		float b_d = (b.position - g_cameraPosForSort).sqrMagnitude;

		if (a_d < b_d)
			return 1;
		else if (a_d > b_d)
			return -1;
		else
			return 0;
	}

	protected static int sortNearFirst (PUEXParticle a, PUEXParticle b)
	{
		float a_d = (a.position - g_cameraPosForSort).sqrMagnitude;
		float b_d = (b.position - g_cameraPosForSort).sqrMagnitude;

		if (a_d < b_d)
			return -1;
		else if (a_d > b_d)
			return 1;
		else
			return 0;
	}

	protected static int sortYoungestFirst (PUEXParticle a, PUEXParticle b)
	{
		float a_t = (a.totalTimeToLive - a.timeToLive);
		float b_t = (b.totalTimeToLive - b.timeToLive);

		if (a_t < b_t)
			return -1;
		else if (a_t > b_t)
			return 1;
		else
			return 0;
	}

	protected static int sortOldestFirst (PUEXParticle a, PUEXParticle b)
	{
		float a_t = (a.totalTimeToLive - a.timeToLive);
		float b_t = (b.totalTimeToLive - b.timeToLive);

		if (a_t < b_t)
			return 1;
		else if (a_t > b_t)
			return -1;
		else
			return 0;
	}
	#endregion

	#region - Param property
	public virtual void updateParams (PUEXParticleSystem system)
	{
	}
	#endregion

	#region - Serialization
	public virtual JsonData save (JsonData emitterData, JsonData systemData)
	{
		JsonData ret = new JsonData ();
		
		ret ["type"] = GetType ().Name;
		ret ["name"] = renderName;

		switch (sortType) {
		case SortType.ST_NONE: {
			ret ["sort_type"] = "none";
			break;
		}
		case SortType.ST_FAR_FIRST: {
			ret ["sort_type"] = "far_first";
			break;
		}
		case SortType.ST_NEAR_FIRST: {
			ret ["sort_type"] = "near_first";
			break;
		}
		case SortType.ST_YOUNGEST_FIRST: {
			ret ["sort_type"] = "youngest_first";
			break;
		}
		case SortType.ST_OLDEST_FIRST: {
			ret ["sort_type"] = "oldest_first";
			break;
		}
		}

#if UNITY_EDITOR
		string texturePath = "texture/" + Path.GetFileName(AssetDatabase.GetAssetPath (texture));
		if (Path.GetExtension(texturePath) == ".tga")
			texturePath += ".ccz";
		ret ["texture"] = texturePath;
#endif

		ret ["auto_start"] = enabled;

		return ret;
	}
	#endregion

}
